﻿/*
 * Stardust Software R&D Group Confidential
 * Project: Vesalius Image Annotation - Name as Salomay
 *
 * (C) Copyright Stardust Software R&D Group. 2013
 *
 * The source code for this program is not published or otherwise divested of 
 * its trade secrets
 */

/**
 * Runtime context storage, you can use it to store session varaibles 
 **/
var session = new Context();

/** Puts default values for session **/
// Shape options
session.set("BORDER_COLOR", "#000000");
session.set("FILL_COLOR", "#805353");
session.set("SHAPE_OPACITY", 1);
session.set("BORDER_WIDTH", 1);

// Brush options
session.set("BRUSH_WIDTH", 1);
session.set("BRUSH_COLOR", "#000000");
session.set("BRUSH_TYPE", "Pencil"); 

// Engine options
session.set("ENGINE_TYPE", "fabric");

// Line options
session.set("CONNECTION_LINE_WIDTH", 2);
session.set("CONNECTION_LINE_COLOR", "#FF0000");
session.set("CONNECTION_LINE_OPACITY", 0.3);

// Text options
session.set("TEXT_COLOR", "#FFFFFF");

// Canvas options
session.set("HIDE_ANNOTATION_WHEN_OPEN", false);

// Component information
session.set("COMPONENT_WIDTH", 1200);
session.set("COMPONENT_HEIGHT", 800);

/**
 ** Salomay Application - Singleton facade for users to action on the application and build all UI components
 ** @author - Cheng Li, Sep 06, 2013
 **/
Salomay.Application = {
	/**
	 ** Properties
	 **/
	toolbarHeight: 30,
	sidebarWidth: 230,
	toolbox: null,
	activeCanvas: null,
	canvasCollection: new Array(),
	
	/**
	 ** Render whole application into DOM
	 ** @param container - The DOM node where the application UI append to
	 ** @author - Cheng Li, Sep 06, 2013
	 **/
    renderTo: function (options) {
	
		var container = options.container;
        var html = "<input type='hidden' id='canvasData'/>"
						+"<table id='mainContainer' style='height:" + (session.get("COMPONENT_HEIGHT") - 30) + "px;width:" +  (session.get("COMPONENT_WIDTH") - 30) + "px;' ><tbody>"
                        + "<tr style='height:" + Salomay.Application.toolbarHeight + "px;'><td id = 'toolbarArea' colspan='2'>" + Salomay.Application._getToolbar() + "</td></tr>"
                        + "<tr><td id='canvasArea' valign='top'><div id='canvasTab1'></div><div id='canvasTab2'></div></td><td id = 'sideArea' valign='top' style='width:" + Salomay.Application.sidebarWidth + "px;'>" + Salomay.Application._getSidebar() + "</td>"
                        + "</tr></tbody></table>";
        container.html(html);
		
		/*** Render UI components ***/
		Salomay.Application._renderToolbar();
		Salomay.Application._renderSidebar();
		Salomay.Application.toolbox = new Salomay.Application.Toolbox();
		Salomay.Application.toolbox.renderTo({container:$("#toolboxPanelContainer")});

		Salomay.Application.optionsPanel = new Salomay.Application.OptionsPanel();
		Salomay.Application.layersPanel = new Salomay.Application.LayersPanel();
		
		Salomay.Application.contextMenu = new Salomay.Application.ContextMenu();
		Salomay.Application.annotationEditor = new Salomay.Application.AnnotationEditor();
		//Salomay.Application.textEditor = new Salomay.Application.TextEditor();
		
		/** Binding events **/
		Salomay.Application._bindWidgetEvents();
    },
	
	/** 
	 * Build events for all widgets
	 **/
	_bindWidgetEvents: function(){
		/*** Bind Events ***/
		// Toolbar button click events
		Salomay.Application.toolbox.addObserver("On_click_toolbarButtons", null, function(context) {
			if (Salomay.Application.activeCanvas) {
				var toolbar = context.sender;
				if (toolbar.activeTool == "arrow") {
					Salomay.Application.activeCanvas.disableFreeDrawingMode();
					Salomay.Application.activeCanvas.disableShapeDrawingMode();
					Salomay.Application.optionsPanel.reset();
				} else if (toolbar.activeTool == Salomay.ShapeFactory.SHAPE_PENCIL) {
					Salomay.Application.activeCanvas.disableShapeDrawingMode();
					Salomay.Application.activeCanvas.enableFreeDrawingMode();
					Salomay.Application.optionsPanel.renderTo({optionsType: "brush", container: $("#optionPanelContainer"), canvas: Salomay.Application.activeCanvas});
				} else if(toolbar.activeTool == Salomay.ShapeFactory.SHAPE_TEXT){
					Salomay.Application.activeCanvas.disableFreeDrawingMode();
					Salomay.Application.activeCanvas.enableShapeDrawingMode();
					Salomay.Application.optionsPanel.renderTo({optionsType: "text", container: $("#optionPanelContainer") ,canvas: Salomay.Application.activeCanvas});
				} else {
					Salomay.Application.activeCanvas.disableFreeDrawingMode();
					Salomay.Application.activeCanvas.enableShapeDrawingMode();
					Salomay.Application.optionsPanel.renderTo({optionsType: "normalShape", container: $("#optionPanelContainer") ,canvas: Salomay.Application.activeCanvas});
				}
			}
		});
		
		// Option panel events
		Salomay.Application.optionsPanel.addObserver("On_brush_changed", null, function(c) {
			if (Salomay.Application.activeCanvas) {
				Salomay.Application.activeCanvas.setBrush({type: session.get("BRUSH_TYPE"), width: session.get("BRUSH_WIDTH"), color: session.get("BRUSH_COLOR")});
			}
		});
		
		// Layer panel events
		Salomay.Application.layersPanel.addObserver("On_layer_selected", null, function(c) {
			if (Salomay.Application.activeCanvas) {
				$( "#btnSelectArrow" ).click();
				var shape = Salomay.Application.activeCanvas.getEngine().getShapeById(c.shapeId);
				Salomay.Application.optionsPanel.renderTo({shape: shape, event: c.event, canvas:Salomay.Application.activeCanvas , container: $("#optionPanelContainer")});
				if (shape.isVisible()) {
					Salomay.Application.activeCanvas.getEngine().setSelectedObject({shape: shape});
				}
			}
		});
		
		// Annotation editor
		Salomay.Application.annotationEditor.addObserver("On_add_annotation", null, function(context) {
			if (Salomay.Application.activeCanvas) {
				var shape = context.shape;
				var txt = context.text;
				if (txt && txt.length > 0) {
					var text = Salomay.ShapeFactory.Create({
						engine: Salomay.Application.activeCanvas.getEngine(),
						type: Salomay.ShapeFactory.SHAPE_TEXT,
						left: shape.getProperty("left") ,
						top: shape.getProperty("top") ,
						text: txt,
						borderColor:'#FFFFFF',
						fillColor:'#FFFFFF',
						fontSize: 14
					});
					Salomay.Application.activeCanvas.getEngine().addShape({shape:text});
					Salomay.Application.activeCanvas.attachShapes(shape, text);
					Salomay.Application.activeCanvas.refresh();
				}
			}
		});
		
		Salomay.Application.annotationEditor.addObserver("On_change_annotation", null, function(context) {
			if (Salomay.Application.activeCanvas) {
				var shape = context.shape;
				var txt = context.text;
				if (txt && txt.length > 0) {
					shape.setProperty("text", txt);
				} else {
					Salomay.Application.activeCanvas.getEngine().removeShape({shape:shape});
				}
				Salomay.Application.activeCanvas.refresh();
			}
		});
	},
	
	/**
	 * Bind events for canvas
	 **/
	_bindCanvasEvents: function(canvas) {
		// Canvas events
		canvas.addObserver("On_canvas_startdrawing", null, function(c) {
			var shapeType = Salomay.Application.toolbox.activeTool;
			var event = c.event;
			var canvas = c.sender;
			canvas.setDrawingShapeType(shapeType);
		});
		
		canvas.addObserver("On_canvas_changed", null, function(c) {
			Salomay.Application.layersPanel.renderTo({shapes:canvas.getEngine().getAllObjects(), container: $("#layerPanelContainer")});
		});
		
		canvas.addObserver("On_canvas_selected", null, function(c) {
			Salomay.Application.optionsPanel.reset();
		});
		
		canvas.addObserver("On_shape_selected", null, function(c) {
			Salomay.Application.optionsPanel.renderTo({shape: c.shape, event: c.event, canvas:c.canvas , container: $("#optionPanelContainer")});
		});
	},
	
	/** 
	 * Build side bar layout html and returns to caller
	 * @return side bar layout html
	 **/
	_getSidebar: function(){
		var html = "<div id='accordion' style='width:260px;'>"
				+ "<h3><span id='toolboxLabel'>Toolbox</span></h3>"
				+ "<div><div id='toolboxPanelContainer' style='height:110px;' align='center'></div></div>"
				+ "<h3><span id='optionLabel'>Options</span></h3>"
				+ "<div><div id='optionPanelContainer' style='height:130px;position:relative;left:-15px;overflow:visible;'></div></div>"
				+ "<h3>Layers</h3>"
				+ "<div><div id='layerPanelContainer' style='height:160px;position:relative;left:-15px;'></div></div>"
				+ "</div>";
				//
		var tabHTML = "<div id='tabs' style='width:280px;'>"
					+ "<ul>"
					+ "<li><a href='#tabs-1'>Annotation</a></li>"
					+ "<li><a href='#tabs-2'>History</a></li>"
					+ "<li><a href='#tabs-3'>Repository</a></li>"
					+ "</ul>"
					+ "<div id='tabs-1'>"
					+ "<p>" + html + "</p>"
					+ "</div>"
					+ "<div id='tabs-2'>"
					+ "<p><div style='height:400px;'>Placeholder for History tab</div></p>"
					+ "</div>"
					+ "<div id='tabs-3'>"
					+ "<p><div style='height:400px;'>Placeholder for Repository tab</div></p>"
					+ "</div>"
					+ "</div>"
		return tabHTML;
	},
	
	/** 
	 * Use JQuery UI accordion widget to render the side bar
	 **/
	_renderSidebar: function(){
		$( "#tabs" ).tabs();
		$( "#accordion" ).multiOpenAccordion({active: 'all' });
	},
	
	/**
	 * Build toolbar layout html and returns to caller
	 * @return toolbar layout html
	 **/
	_getToolbar: function(){
		var html = '<div id="toolbar" class="ui-widget-header ui-corner-all"><table><tbody><tr>'
				+ '<td style="width:10px;"></td>'
				+ '<td><button id="toolbarOpen">Open</button></td>'
				+ '<td><button id="toolbarSave">Save</button></td>'
				+ '<td><button id="toolbarImportImage">Import Image</button></td>'
				+ '<td><button id="toolbarExportImage">Export Image</button></td>'
				+ '<td><button id="toolbarDelete">Delete</button></td>'
				+ '<td><button id="toolbarSettings">Settings</button></td>'
				+ '</tr></tbody></table>'
				+ '</div>';
		return html;
	},
	
	/**
	 * Use JQuery UI button widget to render the side bar and initailize events
	 **/
	_renderToolbar: function(){
		$( "#toolbarNew" ).button({
			text: false,
			icons: {
				primary: "ui-icon-document"
			}
		});
		$( "#toolbarOpen" ).button({
			text: false,
			icons: {
				primary: "ui-icon-folder-open"
			}
		}).click(function(){
			Salomay.Application.gettingStart.show(true);
		});
		$( "#toolbarSave" ).button({
			text: false,
			icons: {
				primary: "ui-icon-disk"
			}
		}).click(function(){
			Salomay.Application.activeCanvas.getEngine().setProperty("lastShapeIndex", Salomay.ShapeFactory.shapeIndex);
			$('#canvasData').val(JSON.stringify(Salomay.Application.activeCanvas.getEngine()));
		});
		$( "#toolbarSaveAs" ).button({
			text: false,
			icons: {
				primary: "ui-icon-newwin"
			}
		});
		$( "#toolbarExportImage" ).button({
			text: false,
			icons: {
				primary: "ui-icon-image"
			}
		});
		$( "#toolbarExportImage" ).button({
			text: false,
			icons: {
				primary: "ui-icon-image"
			}
		});
		$( "#toolbarImportImage" ).button({
			text: false,
			icons: {
				primary: "ui-icon-extlink"
			}
		});
		$( "#toolbarDelete" ).button({
			text: false,
			icons: {
				primary: "ui-icon-closethick"
			}
		});
		$( "#toolbarSettings" ).button({
			text: false,
			icons: {
				primary: "ui-icon-gear"
			}
		});
	},
	
	renderCanvas: function(context) {
		Salomay.Application.activeCanvas = new Salomay.Application.Canvas();
		Salomay.Application._bindCanvasEvents(Salomay.Application.activeCanvas);
		
		var point = Salomay.Application.activeCanvas.getAdjustedCanvasSize({
			imageWidth: context.imageWidth, 
			imageHeight: context.imageHeight,
			componentWidth: session.get("COMPONENT_WIDTH") - Salomay.Application.sidebarWidth - 200,
			componentHeight: session.get("COMPONENT_HEIGHT") - 80
		})
		
		Salomay.Application.activeCanvas.renderTo({container:$("#canvasTab1"), width: point.width, height: point.height});
		if (!context.archiveData || context.archiveData.length <=0) {
			Salomay.Application.activeCanvas.getEngine().setBackgroundImage({url: context.bkImg});
			Salomay.ShapeFactory.reset();
		} else {
			Salomay.Application.activeCanvas.getEngine().loadFromJSON({json: context.archiveData});
			var allShapes = Salomay.Application.activeCanvas.getEngine().getAllObjects();
			for (var i=0; i<allShapes.length;i++) {
				if (allShapes[i].getProperty("isConnectionLine")) {
					Salomay.Application.activeCanvas.getEngine().removeShape({shape:allShapes[i]});
				}
				var attachTo = allShapes[i].getProperty("attachTo");
				if (attachTo) {
					var fromShape = Salomay.Application.activeCanvas.getEngine().getShapeById(attachTo);
					var toShape = allShapes[i];
					Salomay.Application.activeCanvas.attachShapes(fromShape, toShape);
					if (session.get("HIDE_ANNOTATION_WHEN_OPEN")) {
						Salomay.Application.activeCanvas.hideAllAnnotation();
					}
				}
			}
			Salomay.ShapeFactory.shapeIndex = context.archiveData.lastShapeIndex;
		}
		Salomay.Application.activeCanvas.refresh();
	}
}

/**
 ** Abstarct widget class, all UI widget should be extended from this class.
 ** @author -  Cheng Li, Sep 06, 2013
 **/
Salomay.Application.Widget = Class.create();
Salomay.Application.Widget.prototype = {
	/**
	 * Constructor
	 **/
	initialize: function(options){ 
		 this.observerManager = new ObserverManager();
		 this.options = options;
		 this._initialize(options);
	},
	
	/**
	 * Render widget into parent container
	 * @param options JSON options
	 **/
	renderTo: function(options) {
		this._renderTo(options);
		this.bindEvents();
	},
	
	/**
	 * Reset Widget settings and status
	 **/
	reset: function(){
		this._reset();
	},
	
	/**
	 * Bind events after rendering
	 **/
	bindEvents: function(){
		this._bindEvents();
	},
	
 	/**
     * Add event lisenter for the instance
	 * @param event_name the name of observing event
	 * @param evecuter the this pointer for who call the method
	 * @param method event handler
     **/
    addObserver:function(event_name,executer,method){
	    var observer = new Observer(executer,method);
	    this.observerManager.AddObserver(event_name,observer);
    },
	  
    /**
     * Stop observe event
	 * @param event_name the name of observing event
	 * @param evecuter the this pointer for who call the method
	 * @param method event handler
     **/
    stopObserving:function(event_name,executer,method){
        var observer = new Observer(executer,method);
        this.observerManager.StopObserving(event_name,observer);
    },
  
    /**
     * Trigger event by giving event name
	 * @param event_name the name of observing event
	 * @param context context obect contains runtime variables
     **/
    notifyObserver:function(event_name,context){
        this.observerManager.NotifyAllObservers(event_name,context);
    }
}

/** 
 * JQuery dialog wrapper, inherit from Salomay.Application.Widget to standard the interface
 * @author Cheng Li, Sep 11, 2013
 **/
Salomay.Application.Dialog = Class.create();
Salomay.Application.Dialog.prototype = Class.extend({
	/**
	 * Constructor
	 **/
	_initialize: function(options){
		
	},
	
	/**
	 * Render dialog to container
	 * @param options - JSON options requires container, innerHTML and some other optional parameters
	 **/
	_renderTo: function(options) {
		var container = options.container;
		var innerHTML = options.innerHTML;
		if (container) {
			container.html(innerHTML);
			this.dialog = container.dialog(options);
		}
	},
	
	/**
	 * Bind events after the dialog created
	 **/
	_bindEvents: function(){
	},
	
	/**
	 * Hide dialog from application 
	 * @param fade boolean variable set it to true to enable fade effects
	 **/
	hide: function(fade){
		if (!fade) {
			this.dialog.hide();
		} else {
			this.dialog.fadeOut();
		}
	},
	
	/**
	 * Show dialog from application 
	 * @param fade boolean variable set it to true to enable fade effects
	 **/
	show: function(fade){
		if (!fade) {
			this.dialog.show();
		} else {
			this.dialog.fadeIn();
		}
	},
	
	/**
	 * Close dialog from application
	 **/
	close: function(){
		this.dialog.dialog("close");
	}
},Salomay.Application.Widget.prototype);
	

/**
 * Application canvas class, this is proxy for rendering engine and html canvas node
 * it supplies full functionalities to application.
 * @author Cheng Li, Sep 06, 2013 
 **/
Salomay.Application.Canvas = Class.create();
Salomay.Application.Canvas.prototype = Class.extend({
	/**
	 * Constructor
	 **/
	_initialize: function(options){
		 this.id = (options && options.id) ? options.id : "_1";
		 this.engine = null;
		 this.freeDrawMode = false;
		 this.shapeDrawMode = false;
		 this.drawCoverDiv = null;
		 this.drawCoverIframe = null;
		 this.canvasWidth = 1224;
		 this.canvasHeight = 685;
		 this.drawingShape = null;
		 this.shapeClipboard = new Array();
	},
	
	/**
	 * Render canvas into application canvas area
	 * @param options rendering options
	 **/
	_renderTo:function(options){
		var container = options.container;
		if (options) {
			if (options.width) this.canvasWidth = options.width;
			if (options.height)this.canvasHeight = options.height;
		}
		container.html(this._getCanvas({width:this.canvasWidth, height:this.canvasHeight}));
		this.engine = this._engineFactory(session.get("ENGINE_TYPE"));
		this.canvasLocation = Utils.getElementLocationInViewPort($("#canvas" + this.id));
		this._bindEvents();
		this.notifyObserver("On_canvas_changed", {sender:this});
		$( "#btnSelectArrow" ).click();
	},
	
	/**
	 * Get canvas layout html
	 * @param options layout informations
	 * @return canvas layout html
	 **/
	_getCanvas: function(options){
		var html = "<div id='canvasFrame" + this.id + "' class='app-canvas-shadow' style=' position: relative;left:10px;top:10px;width:" + options.width + "px;height:" + options.height + "px;z-index:10;'>"
				+ "<canvas id='canvas" + this.id + "' class='app-canvas' width='" + options.width + "px' height='" + options.height + "px' style='position:absolute;left:0px;top:0px;border:1px solid #A6C9E2;z-index:11;'></canvas>"
				+ "</div>";
		return html;
	},
	
	/**
	 * Factory method, build different engine by given type
	 * @param engineType use it to specify which engine you want
	 * @param Salomay rendering engine
	 **/
	_engineFactory: function(engineType) {
		switch(engineType) {
			case "fabric":
			default:
				var canvas = new Salomay.FabricEngineAdapter({domNodeId: 'canvas' + this.id, isDrawingMode: false, backgroundColor : '#FFFFFF', selection: false});
				return canvas;
			break;
		}
	},
	
	/**
	 * Bind events after canvas render to application
	 **/
	_bindEvents: function(){
		/** Event handler for mouse:down, two kinds of things may happen 
		 *	 1.) If not in shapeDrawMode, only dispatch the event to observer
		 *	 2.) If in shapeDrawMode, add shape to canvas and start drawing shape
		 **/
		this.engine.on({eventName:'mouse:down', callback:(function(options) {
			var event = options.event;
			Salomay.Application.contextMenu.close();
			// If shapeDrawMode is disabled, fire On_shape_selected event
			if (!this.shapeDrawMode && options.target) {
				this.notifyObserver("On_shape_selected", {sender:this, shape: options.target, canvas: this ,shapeType: options.target.getShapeType(), event:event});
				//options.target.notifyObserver("On_shape_selected", {sender: this, event:event});
			} else if (!this.shapeDrawMode && !options.target && !this.getEngine().isDrawingMode()) {
				this.notifyObserver("On_canvas_selected", {sender:this, canvas: this , event:event});
			} else if (this.shapeDrawMode){
				// If shapeDrawMode is enabled, discard selected object first and then start drawing object
				if (this.engine.getSelectedObject()) {
					// Store the selected shape and then restore the selected shape attributes after mouse up
					this.preservedSelectedShape = this.engine.getSelectedObject();
					this.engine.discardAllSelectedObjects();
					this.preservedSelectedShape.lockMovement();
				}
				
				// Start drawing
				if (!this.drawingShape) {
					// Fire On_canvas_startdrawing events
					this.notifyObserver("On_canvas_startdrawing", {sender:this, event:event});
					if (this.getDrawingShapeType() != Salomay.ShapeFactory.SHAPE_TEXT) {
						// Get canvas location and use to calculate the location of new added shape
						var canvasLocation = this.canvasLocation;
						
						// Create shape object from shape factory
						this.drawingShape = Salomay.ShapeFactory.Create({
							engine: this.engine,
							type: this.getDrawingShapeType(),
							left: event.pageX - canvasLocation.x,
							top: event.pageY - canvasLocation.y,
							borderWidth: session.get("BORDER_WIDTH"),
							borderColor: session.get("BORDER_COLOR"),
							fillColor: session.get("FILL_COLOR"),
							opacity: session.get("SHAPE_OPACITY")
						});
						
						// Store the mouse position
						this.mouseX = event.pageX;
						this.mouseY = event.pageY;
						
						// Render new added shape to canvas
						this.engine.addShape({shape:this.drawingShape})
						this.engine.renderAll();
					} else if(this.getDrawingShapeType() == Salomay.ShapeFactory.SHAPE_TEXT){
						//Salomay.Application.textEditor.renderTo({container:$('#textInputWidget'), left:event.pageX-10, top:event.pageY-10, height:50, width:100});
						this.engine.renderAll();
					}
				}
			}
		}).bind(this)});
		
		/** 
		 ** Event handler for mouse:move, adjust shape size with mouse move offsets (x and y)
		 **/
		this.engine.on({eventName:'mouse:move', callback:(function(options) {
			var event = options.event;
			// Scale shapes during drag mouse on canvas
			if (this.shapeDrawMode && this.drawingShape) {
				this.notifyObserver("On_canvas_drawshape", {sender:this, event:event});
				var xOffset = (event.pageX - this.mouseX);
				var yOffset = (event.pageY - this.mouseY);
				this.drawingShape.scaleWithDrag(xOffset, yOffset);
				this.engine.renderAll();
			}
		}).bind(this)});
		
		/** 
		 ** Event handler for mouse:up, stop drawing shape
		 **/
		this.engine.on({eventName:'mouse:up', callback:(function(options) {
			var event = options.event;
			// Handle right click
			if (event.button == 2) {
				//if (!this.shapeDrawMode) {
					var shape = this.engine.findTarget(event);
					if (shape) {
						$( "#btnSelectArrow" ).click();
						this.engine.setSelectedObject({shape: shape});
						this.notifyObserver("On_shape_selected", {sender:this, shape: shape, canvas: this ,shapeType: shape.getShapeType(), event:event});
						Salomay.Application.contextMenu.renderTo({
							shape:shape, 
							container:$('#contextMenu'),
							canvas:this,
							left: event.pageX,
							top: event.pageY
						});
					} else {
						//Salomay.Application.contextMenu.close();
						Salomay.Application.contextMenu.renderTo({
							shape:null, 
							container:$('#contextMenu'),
							canvas:this,
							left: event.pageX,
							top: event.pageY
						});
					}
				//}
			} else {
				this.notifyObserver("On_canvas_changed", {sender:this});
				// Stop drawing when mouse up on canvas
				if (this.shapeDrawMode && this.drawingShape) {
					// Remove the new added shape if new drag action performed
					if (Math.abs(event.pageX - this.mouseX) < 1 || Math.abs(event.pageY - this.mouseY) < 1) {
						this.engine.removeShape({shape:this.drawingShape});
					}
					
					// Fire On_canvas_stopdrawingshape event
					this.notifyObserver("On_canvas_stopdrawingshape", {sender:this, event:event});
					
					// Set drawingShape to null
					this.drawingShape = null;
				} else if(this.getEngine().isDrawingMode()){
					Salomay.Application.activeCanvas.setBrush({type: session.get("BRUSH_TYPE"), width: session.get("BRUSH_WIDTH"), color: session.get("BRUSH_COLOR")});
				}
				
				// Restore selected shape attribute and remove the preservedSelectedShape variable
				if (this.preservedSelectedShape) {
					this.preservedSelectedShape.unlockMovement();
					this.preservedSelectedShape = null;
				}
			}
		}).bind(this)});
		
		/** 
		 ** Event handler for document keyup, remove selected shape from mode
		 **/
		document.onkeyup = (function(evt) {
			var event = evt || window.event;
			var code = event.keyCode || event.charCode;
			var selectedShape = this.engine.getSelectedObject();
			var element = event.target || event.srcElement;
			if (element.id == "shapeId") return;
			
			// keyboard actions
			if (selectedShape) {
				if (code == 46) {
					this.engine.removeShape({shape:selectedShape});
					this.notifyObserver("On_canvas_changed", {sender:this});
				}
				
			}
		}).bind(this);
	},
	
	/**
	 * Reset canvas
	 **/
	_reset: function(){
	
	},
	
	/**
	 * Turn on free drawing mode 
	 **/
	enableFreeDrawingMode: function(){
		this.disableShapeDrawingMode();
		this.engine.enableDrawingMode();
	},
	
	/**
	 * Turn off free drawing mode 
	 **/
	disableFreeDrawingMode: function(){
		this.engine.disableDrawingMode();
	},
	
	/**
	 * Turn on shape drawing mode
	 **/
	enableShapeDrawingMode: function(){
		this.shapeDrawMode = true;
	},
	
	/**
	 * Turn off shape drawing mode
	 **/
	disableShapeDrawingMode: function(){
		this.shapeDrawMode = false;
	},
	
	/**
	 * Set current drawing type when drag mouse on canvas, at same time only one type can be set
	 * @param type type of shape
	 **/
	setDrawingShapeType: function (type) {
		this.drawingShapeType = type;
	},
	
	/**
	 * Get current drawing type, by default it is rectangle
	 * @return type type of shape
	 **/
	getDrawingShapeType: function(){
		if (this.drawingShapeType) return this.drawingShapeType;
		return Salomay.ShapeFactory.SHAPE_RECT;
	},
	
	/**
	 * Get current rendering engine
	 * @return Salomay rendering engine object
	 **/
	getEngine: function(){
		return this.engine;
	},
	
	/**
	 * Set canvas brush information, this method posts the options to engine
	 * @options JSON options
	 **/
	setBrush: function(options) {
		this.engine.setBrush(options);
	},
	
	/** 
	 * Attach two shapes with connection line
	 * @param fromShape main shape
	 * @param toShape attach shape
	 **/
	attachShapes: function(fromShape, toShape) {
		var engine = this.engine;
		
		// Set up connection with attachedBy and attachTo properties
		// they will be include in JSON when serialize canvas
		fromShape.setProperty("attachedBy", toShape.getId());
		toShape.setProperty("attachTo", fromShape.getId());
		
		// Create connection line
		var connectionLine = Salomay.ShapeFactory.Create({
				engine: this.getEngine(),
				type: Salomay.ShapeFactory.SHAPE_LINE,
				points: [fromShape.getCenterPoint().x, fromShape.getCenterPoint().y, toShape.getCenterPoint().x, toShape.getCenterPoint().y],
				fillColor: session.get("CONNECTION_LINE_COLOR"),
				borderColor: session.get("CONNECTION_LINE_COLOR"),
				borderWidth: session.get("CONNECTION_LINE_WIDTH"),
				opacity: session.get("CONNECTION_LINE_OPACITY"),
				selectable: false
		});
		
		// Set the isConnectionLine flag it will be include in JSON when serialize canvas
		connectionLine.setProperty("isConnectionLine", true);
		if (!toShape.isVisible()) {
			connectionLine.hide();
		}
		
		// Store the connection line object in fromShape and toShape so they can know which line belongs to them
		fromShape.setProperty("connectionLine", connectionLine);
		toShape.setProperty("connectionLine", connectionLine);
		
		// Bind events, when the main shape removed from canvas
		// both the connection line and the attached shape should be removed.
		fromShape.on({eventName:"removed", callback: function(){
			var attachedBy = fromShape.getProperty("attachedBy");
			if (attachedBy && attachedBy.length > 0) {
				var shape = engine.getShapeById(attachedBy);
				if (shape) {
					shape.setProperty("attachTo", false);
					engine.removeShape({shape:shape.getProperty("connectionLine")});
					shape.setProperty("connectionLine", false);
					engine.removeShape({shape:shape});
				}
			}
		}});
		
		// Bind events, when the attach shape removed from canvas
		// only remove the connection line and release the connection relation
		toShape.on({eventName:"removed", callback: function(){
			var attachTo = toShape.getProperty("attachTo");
			if (attachTo && attachTo.length > 0) {
				var shape = engine.getShapeById(attachTo);
				if (shape) {
					shape.setProperty("attachedBy", false);
					engine.removeShape({shape:shape.getProperty("connectionLine")});
					shape.setProperty("connectionLine", false);
				}
			}
		}});
		
		// Adjust angle and lenght of connection line when from/to shape moving/scaling
		fromShape.on({eventName:"moving", callback: function(){
				var line = fromShape.getProperty("connectionLine");
				if (line) {
					line.setProperty("x1", fromShape.getCenterPoint().x);
					line.setProperty("y1", fromShape.getCenterPoint().y);
					line.setProperty("x2", toShape.getCenterPoint().x);
					line.setProperty("y2", toShape.getCenterPoint().y);
				}
		}});
		
		toShape.on({eventName:"moving", callback: function(){
			var line = fromShape.getProperty("connectionLine");
			if (line) {
				line.setProperty("x1", fromShape.getCenterPoint().x);
				line.setProperty("y1", fromShape.getCenterPoint().y);
				line.setProperty("x2", toShape.getCenterPoint().x);
				line.setProperty("y2", toShape.getCenterPoint().y);
			}
		}});
		
		fromShape.on({eventName:"scaling", callback: function(){
			var line = fromShape.getProperty("connectionLine");
			if (line) {
				line.setProperty("x1", fromShape.getCenterPoint().x);
				line.setProperty("y1", fromShape.getCenterPoint().y);
				line.setProperty("x2", toShape.getCenterPoint().x);
				line.setProperty("y2", toShape.getCenterPoint().y);
			}
		}});
		
		toShape.on({eventName:"scaling", callback: function(){
			var line = fromShape.getProperty("connectionLine");
			if (line) {
				line.setProperty("x1", fromShape.getCenterPoint().x);
				line.setProperty("y1", fromShape.getCenterPoint().y);
				line.setProperty("x2", toShape.getCenterPoint().x);
				line.setProperty("y2", toShape.getCenterPoint().y);
			}
		}});
		
		// Add connection line to engine
		engine.addShape({shape:connectionLine});
	},
	
	/**
	 * Show annotation for all shapes on canvas
	 **/
	showAllAnnotation: function(){
		var shapes = this.getEngine().getAllObjects();
		for (var i=0; i<shapes.length;i++) {
			var shape = shapes[i];
			try {
				var attachElementId = shape.getProperty("attachedBy");
				if (attachElementId) {
					var connectionLine = shape.getProperty("connectionLine");
					this.getEngine().getShapeById(attachElementId).show();
					connectionLine.show();
				}
			} catch (e) {}
		}
		this.getEngine().renderAll();
	},
	
	/**
	 * Hide annotation for all shapes on canvas
	 **/
	hideAllAnnotation: function(){
		var shapes = this.getEngine().getAllObjects();
		for (var i=0; i<shapes.length;i++) {
			var shape = shapes[i];
			try {
				var attachElementId = shape.getProperty("attachedBy");
				if (attachElementId) {
					var connectionLine = shape.getProperty("connectionLine");
					this.getEngine().getShapeById(attachElementId).hide();
					connectionLine.hide();
				}
			} catch (e) {}
		}
		this.getEngine().renderAll();
	},
	
	/**
	 * Copy shape you select
	 * @param shape copying shape
	 **/
	copyShape: function(shape){
		if (shape.getProperty("attachedBy")) {
			var newShape = shape.clone();
			var newShapeAttached = this.getEngine().getShapeById(shape.getProperty("attachedBy")).clone();
			newShape.setProperty("attachedBy", newShapeAttached.getId());
			newShapeAttached.setProperty("attachTo", newShape.getId());
			this.shapeClipboard[0] = newShape;
			this.shapeClipboard[1] = newShapeAttached;
		} else {
			this.shapeClipboard[0] = shape.clone();
		}
	},
	
	/**
	 * Cut shape you select
	 * @param shape cutting shape
	 **/
	cutShape: function(shape){
		if (shape.getProperty("attachedBy")) {
			this.shapeClipboard[0] = shape;
			this.shapeClipboard[1] = this.getEngine().getShapeById(shape.getProperty("attachedBy"));
		} else {
			this.shapeClipboard[0] = shape;
		}
		this.getEngine().removeShape({shape:shape});
	},
	
	/**
	 * Paste shape into canvas
	 * @param event contains coordinates data to render the clone shape to that position
	 **/
	pasteShape: function(evt){
		if (this.shapeClipboard.length <=0 )return;
		var xOffset =  evt.pageX - this.canvasLocation.x - Number(this.shapeClipboard[0].getProperty("left"));
		var yOffset =  evt.pageY - this.canvasLocation.y - Number(this.shapeClipboard[0].getProperty("top"));
		for (var i = 0; i < this.shapeClipboard.length; i++) {
			// calculate position
			this.shapeClipboard[i].setProperty("left", Number(this.shapeClipboard[i].getProperty("left")) + xOffset);
			this.shapeClipboard[i].setProperty("top", Number(this.shapeClipboard[i].getProperty("top")) + yOffset);

			this.getEngine().addShape({shape:this.shapeClipboard[i]});
		}
		if (this.shapeClipboard.length == 2) {
			this.attachShapes(this.shapeClipboard[0], this.shapeClipboard[1]);
		}
		this.shapeClipboard = new Array();
		this.getEngine().renderAll();
	},
	
	/**
	 * Check if there is something in clipboard
	 * @return return true if something can be pasted else return false
	 **/
	hasShapeToPaste: function(){
		return this.shapeClipboard.length > 0;
	},
	
	/**
	 * Refresh all elements on canvas and fire the On_canvas_changed event
	 **/
	refresh: function(options){
		this.getEngine().renderAll();
		this.notifyObserver("On_canvas_changed", {sender:this,options:options});
	},
	
	/**
	 * Get adjusted size of canvas
	 * @return JSON {width: w, height: h}
	 **/
	getAdjustedCanvasSize: function(options) {
		var componentHeight = Number(options.componentHeight);
		var componentWidth = Number(options.componentWidth);
		var imageHeight = Number(options.imageHeight);
		var imageWidth = Number(options.imageWidth);
		
		if (componentHeight >= imageHeight && componentWidth >= imageWidth) {
			return {height: imageHeight, width: imageWidth};
		} else {
			var wRate = imageWidth / (imageWidth + imageHeight);
			var hRate = imageHeight / (imageWidth + imageHeight);
			var width = parseInt((componentWidth + componentHeight) * wRate);
			var height = parseInt((componentWidth + componentHeight) * hRate);
			return {height: height, width: width};
		}
	}
	
},Salomay.Application.Widget.prototype);

/**
 * Toolbox class on top of application
 * @author Cheng Li, Sep 06, 2013
 **/
Salomay.Application.Toolbox = Class.create();
Salomay.Application.Toolbox.prototype = Class.extend({
	/**
	 * Constructor
	 **/
	_initialize: function(options){
		 this.activeTool = "arrow";
	},
	
	/**
	 * Build toolbox layout html and render it to application, then build default events
	 **/
	_renderTo:function(options){
		var container = options.container;
		var html = '<table id="toolbox" border="0" cellspacing="0" cellpadding="0">'
				 +  '<tr id="toolboxRow1">'
				 +	'<td><input type="radio" id="btnSelectArrow" name="tool" checked="checked" /><label for="btnSelectArrow" style="width:80px;">Arrow</label></td>'
				 +	'<td><input type="radio" id="btnRectangle" name="tool" /><label for="btnRectangle" style="width:80px;">Rectangle</label></td>'
				 +	'</tr>'
				 +  '<tr>'
				 +	'<td><input type="radio" id="btnCircle" name="tool"/><label for="btnCircle" style="width:80px;">Circle</label></td>'
				 +	'<td><input type="radio" id="btnTriangle" name="tool" /><label for="btnTriangle" style="width:80px;">Triangle</label></td>'
				 +	'</tr>'
				 +  '<tr>'
				 +	'<td><input type="radio" id="btnLine" name="tool"/><label for="btnLine" style="width:80px;">Line</label></td>'
				 +	'<td><input type="radio" id="btnPolygon" name="tool" /><label for="btnPolygon" style="width:80px;">Ploygon</label></td>'
				 +	'</tr>'
				  +  '<tr>'
				 +	'<td><input type="radio" id="btnPencil" name="tool"/><label for="btnPencil" style="width:80px;">Pencil</label></td>'
				 +	'<td><input type="radio" id="btnText" name="tool" /><label for="btnText" style="width:80px;">Text</label></td>'
				 +	'</tr>'
				 +	'</table>'
		container.html(html);

		$( "#btnSelectArrow" ).button({
			text: true,
			icons: {
				primary: "ui-icon ui-icon-arrowthick-1-nw"
			}
		}).click((function() {
			this.activeTool = "arrow";
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
		
		$( "#btnRectangle" ).button({
			text: true
		}).click((function() {
			this.activeTool = Salomay.ShapeFactory.SHAPE_RECT;
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
		
		$( "#btnCircle" ).button({
			text: true
		}).click((function() {
			this.activeTool = Salomay.ShapeFactory.SHAPE_CIRCLE;
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
		
		$( "#btnTriangle" ).button({
			text: true
		}).click((function() {
			this.activeTool = Salomay.ShapeFactory.SHAPE_TRIANGLE;
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
		
		$( "#btnLine" ).button({
			text: true
		}).click((function() {
			this.activeTool = Salomay.ShapeFactory.SHAPE_LINE;
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
		
		$( "#btnPolygon" ).button({
			text: true
		}).click((function() {
			this.activeTool = Salomay.ShapeFactory.SHAPE_POLYGON;
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
		
		$( "#btnPencil" ).button({
			text: true,
			icons: {
				primary: "ui-icon ui-icon-pencil"
			}
		}).click((function() {
			this.activeTool = Salomay.ShapeFactory.SHAPE_PENCIL;
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
		
		$( "#btnText" ).button({
			text: true
		}).click((function() {
			this.activeTool = Salomay.ShapeFactory.SHAPE_TEXT;
			this.notifyObserver("On_click_toolbarButtons", {sender:this});
		}).bind(this));
	},
	
	/**
	 * Reset toolbar status
	 **/
	_reset: function(){
	
	},
	
	/**
	 * Build events
	 **/
	_bindEvents: function(){
	
	}
},Salomay.Application.Widget.prototype);


/**
 * Widget of options panel on left side of application. This class implements all kinds of rendering
 * options into panel for pencil, text and shape
 * @author Cheng Li, Sep 08, 2013
 **/
Salomay.Application.OptionsPanel = Class.create();
Salomay.Application.OptionsPanel.prototype = Class.extend({
	/**
	 * Constructor
	 **/
	_initialize: function(options){
		 this.selectedShape = null;
		 this.canvas = null;
	},
	
	/**
	 * Render options control into options panel according to different optionsType
	 * optionsType can be 'normalShape', 'drawingLine', 'brush' or 'text'
	 * @param - options options JSON
	 **/
	_renderTo:function(options){
		var container = options.container;
		this.container = container;
		var optionsType = options.optionsType ? options.optionsType : "normalShape";
		this.selectedShape = options.shape;
		if (this.selectedShape && this.selectedShape.getShapeType() == Salomay.ShapeFactory.SHAPE_PATH) {
			optionsType = "brush";
		} else if (this.selectedShape && this.selectedShape.getShapeType() == Salomay.ShapeFactory.SHAPE_TEXT) {
			optionsType = "text";
		}
		this.canvas = options.canvas;
		var optionsHTML = "";
		var optionsArray = new Array();
		
		if (optionsType == "normalShape") {
			optionsArray = this._getNormalShapeOptions();
		} else if (optionsType == "drawingLine") {
			optionsArray = this._getDrawingLineOptions();
		} else if (optionsType == "brush") {
			optionsArray = this._getBrushOptions();
		} else if (optionsType == "text") {
			optionsArray = this._getTextOptions();
		}
		
		var optionHTML = "";
		for (var i=0; i < optionsArray.length; i++) {
			optionHTML += "<tr><td><span style='white-space:nowrap;'>" + optionsArray[i].label + "</span></td><td>" + optionsArray[i].html + "</td></tr>";
		}
		
		var html = "<table id = 'optionsPanel'><tbody>"
				+ optionHTML
				+ "</tbody></table>";
		container.html(html);
		
		// Initialize controlls
		if (optionsType == "normalShape") {
			this._initializeNormalShapeControls();
		} else if (optionsType == "drawingLine") {
			this._initializeLineControls();
		} else if (optionsType == "brush") {
			this._initializeBrushControls();
		} else if (optionsType == "text") {
			this._initializeTextControls();
		}
	},
	
	/**
	 * Reset options panel
	 **/
	_reset: function(){
		this.container.html("");
	},
	
	/**
	 * Build events
	 **/
	_bindEvents: function(){
	
	},
	
	/**
	 * Get the options array for normal shapes
	 * @return options JSON array
	 **/
	_getNormalShapeOptions: function(){
		
		var optionArray = new Array();
		if (this.selectedShape) optionArray.push({label: "ID: ", html: "<input class='app-form-field' id='shapeId' value='" + this.selectedShape.getProperty("id") + "' style='width:80px;'/>"});
		/*
		if (this.selectedShape) optionArray.push({label: "Visible: ", html: "<select class='app-form-field' id='shapeVisible' style='width:80px;'><option value='true'>True</option><option value='false'>False</option></select>"});
		if (this.selectedShape && this.canvas) {
			var shapes = this.canvas.getEngine().getAllObjects();
			var shapeList = "<option value=''></option>";
			if (shapes.length > 1) {
				for (var i = 0; i < shapes.length; i++) {
					if (shapes[i].getProperty("id") == this.selectedShape.getProperty("id")) continue;
					shapeList += "<option value='" + shapes[i].getProperty("id") + "'>" + shapes[i].getProperty("id") + "</option>";
				}
			
				optionArray.push({label: "Attach to: ", html: "<select class='app-form-field' id='attachToShape' style='width:80px;'>" + shapeList + "</select>"});
			}
		}
		*/
		optionArray.push({label: "Border width: ", html: "<div id='borderWidth' style='width:100px;'></div>"});
		optionArray.push({label: "Border color: ", html: "<input id='borderColorButton'>"});
		optionArray.push({label: "Fill color: ", html: "<input id='fillColorButton'/>"});
		optionArray.push({label: "Opacity: ", html: "<div id='shapeOpacity' style='width:100px;'></div>"});	
		if (this.selectedShape) {
			var buttonHTML = "<table border='0' cellspacing='0' cellpadding='0'><tr><td><button id='btnFront' class='btn' style='height:28px;width:60px;'>Up</button></td><td style='width:5px;'>&nbsp;</td><td><button id='btnBack' class='btn' style='height:28px;width:60px;'>Down</button></td></tr></table>";
			optionArray.push({label: "Z-index: ", html: buttonHTML});	
		}	
		if (this.selectedShape) {
			var buttonHTML = "<table border='0' cellspacing='0' cellpadding='0'><tr><td><button id='btnTop' class='btn btn-info' style='height:28px;width:60px;'>Top</button></td><td style='width:5px;'>&nbsp;</td><td><button id='btnBottom' class='btn btn-info' style='height:28px;width:60px;'>Bottom</button></td></tr></table>";
			//optionArray.push({label: "", html: buttonHTML});
		}
		return optionArray;
	},
	
	/**
	 * Get the options array for text
	 * @return options JSON array
	 **/
	_getTextOptions: function(){
		var optionArray = new Array();
		if (this.selectedShape) optionArray.push({label: "ID: ", html: "<input class='app-form-field' id='shapeId' value='" + this.selectedShape.getProperty("id") + "' style='width:80px;'/>"});;
		optionArray.push({label: "Text color: ", html: "<input id='textColorButton'>"});
		optionArray.push({label: "Opacity: ", html: "<div id='shapeOpacity' style='width:100px;'></div>"});	
		if (this.selectedShape) {
			var buttonHTML = "<table border='0' cellspacing='0' cellpadding='0'><tr><td><button id='btnFront' class='btn' style='height:28px;width:60px;'>Up</button></td><td style='width:5px;'>&nbsp;</td><td><button id='btnBack' class='btn' style='height:28px;width:60px;'>Down</button></td></tr></table>";
			optionArray.push({label: "Z-index: ", html: buttonHTML});	
		}	
		if (this.selectedShape) {
			var buttonHTML = "<table border='0' cellspacing='0' cellpadding='0'><tr><td><button id='btnTop' class='btn btn-info' style='height:28px;width:60px;'>Top</button></td><td style='width:5px;'>&nbsp;</td><td><button id='btnBottom' class='btn btn-info' style='height:28px;width:60px;'>Bottom</button></td></tr></table>";
			//optionArray.push({label: "", html: buttonHTML});
		}
		return optionArray;
	},
	
	
	/**
	 * Get the options array for brush (free drawing mode)
	 * @return options JSON array
	 **/
	_getBrushOptions: function(){
		var optionArray = new Array();
		if (this.selectedShape) optionArray.push({label: "ID: ", html: "<input class='app-form-field' id='shapeId' value='" + this.selectedShape.getProperty("id") + "' style='width:80px;'/>"});
		/*
		if (this.selectedShape) optionArray.push({label: "Visible: ", html: "<select class='app-form-field' id='shapeVisible' style='width:80px;'><option value='true'>True</option><option value='false'>False</option></select>"});
		if (this.selectedShape && this.canvas) {
			var shapes = this.canvas.getEngine().getAllObjects();
			var shapeList = "<option value=''></option>";
			if (shapes.length > 1) {
				for (var i = 0; i < shapes.length; i++) {
					if (shapes[i].getProperty("id") == this.selectedShape.getProperty("id")) continue;
					shapeList += "<option value='" + shapes[i].getProperty("id") + "'>" + shapes[i].getProperty("id") + "</option>";
				}
			
				optionArray.push({label: "Attach to: ", html: "<select class='app-form-field' id='attachToShape' style='width:80px;'>" + shapeList + "</select>"});
			}
		}
		*/
		if (!this.selectedShape)optionArray.push({label: "Brush: ", html: "<select class='app-form-field' id='brushType' style='width:100px;'><option value='Pencil'>Pencil</option><option value='Spray'>Spray</option><option value='Circle'>Circle</option></select>"});
		if (!this.selectedShape || !this.selectedShape.isSpray())optionArray.push({label: "Size: ", html: "<div id='brushWidth' style='width:100px;'></div>"});
		optionArray.push({label: "Color: ", html: "<input id='brushColor'/>"});				
		if (this.selectedShape) {
			var buttonHTML = "<table border='0' cellspacing='0' cellpadding='0'><tr><td><button id='btnFront' class='btn' style='height:28px;width:60px;'>Up</button></td><td style='width:5px;'>&nbsp;</td><td><button id='btnBack' class='btn' style='height:28px;width:60px;'>Down</button></td></tr></table>";
			optionArray.push({label: "Z-index: ", html: buttonHTML});	
		}	
		if (this.selectedShape) {
			var buttonHTML = "<table border='0' cellspacing='0' cellpadding='0'><tr><td><button id='btnTop' class='btn btn-info' style='height:28px;width:60px;'>Top</button></td><td style='width:5px;'>&nbsp;</td><td><button id='btnBottom' class='btn btn-info' style='height:28px;width:60px;'>Bottom</button></td></tr></table>";
			//optionArray.push({label: "", html: buttonHTML});
		}
		return optionArray;
	},
	
	/**
	 * Generate the controls and bind events for normal shapes
	 **/
	_initializeNormalShapeControls: function(){
		$("#borderColorButton").val(this.selectedShape ? this.selectedShape.getBorderColor() :session.get("BORDER_COLOR"));
		$("#borderColorButton").spectrum({
			color: this.selectedShape ? this.selectedShape.getBorderColor() : session.get("BORDER_COLOR"),
			showInput: true,
			showPalette: true,
			palette: [
				['black', 'white', 'blanchedalmond'],
				['rgb(255, 128, 0);', 'hsv 100 70 50', 'lightyellow']
			],
			change: (function(color) {
				color = color.toString();
				$("#borderColorButton").val(color);
				if (this.selectedShape) {
					this.selectedShape.setBorderColor(color);
					this.canvas.getEngine().renderAll();
				} else {
					session.set("BORDER_COLOR", color);
				}
			}).bind(this)
		});
		
		$("#fillColorButton").val(this.selectedShape ? this.selectedShape.getFillColor() : session.get("FILL_COLOR"));
		$("#fillColorButton").spectrum({
			color: this.selectedShape ? this.selectedShape.getFillColor() : session.get("FILL_COLOR"),
			showInput: true,
			showPalette: true,
			palette: [
				['black', 'white', 'blanchedalmond'],
				['rgb(255, 128, 0);', 'hsv 100 70 50', 'lightyellow']
			],
			change: (function(color) {
				color = color.toString();
				$("#fillColorButton").val(color);
				if (this.selectedShape) {
					this.selectedShape.setFillColor(color);
					this.canvas.getEngine().renderAll();
				} else {
					session.set("FILL_COLOR", color);
				}
			}).bind(this)
		});
		
		$("#borderWidth").slider({
		  range: "min",
		  value: this.selectedShape ? this.selectedShape.getBorderWidth() : session.get("BORDER_WIDTH"),
		  min: 0,
		  max: 20,
		  slide: (function(event, ui) {
			if (this.selectedShape) {
				if (this.selectedShape.getShapeType() == Salomay.ShapeFactory.SHAPE_CIRCLE) {
					this.selectedShape.setBorderWidth(Number(ui.value)/50);
				} else {
					this.selectedShape.setBorderWidth(ui.value);
				}
				this.canvas.getEngine().renderAll();
			} else {
				session.set("BORDER_WIDTH", ui.value);
			}
		  }).bind(this)
		});
		
		$("#shapeOpacity").slider({
		  range: "min",
		  value: this.selectedShape ? (Number(this.selectedShape.getOpacity()) * 10) :(session.get("SHAPE_OPACITY") * 10),
		  min: 1,
		  max: 10,
		  slide: (function(event, ui) {
			if (this.selectedShape) {
				this.selectedShape.setOpacity(Number(ui.value)/10);
				this.canvas.getEngine().renderAll();
			} else {
				session.set("SHAPE_OPACITY", Number(ui.value)/10);
			}
		  }).bind(this)
		});
		
		if (this.selectedShape) {
			$('#btnBack').on('mousedown', (function(){
				this.selectedShape.sendBackwards();
			}).bind(this));
			
			$('#btnFront').on('mousedown', (function(){
				this.selectedShape.bringForward();
			}).bind(this));
			$('#shapeId').on('change', (
				function() {
					this.updateShapeId($('#shapeId').val());
				}
			).bind(this));
		}
	},
	
	/**
	 * Generate the controls and bind events for brush (free drawing mode)
	 **/
	_initializeBrushControls: function(){
		$('#brushType').val(session.get("BRUSH_TYPE"));
		$('#brushType').on("change", (function(){
			session.set("BRUSH_TYPE", $('#brushType').val());
			this.notifyObserver("On_brush_changed", {sender:this});
		}).bind(this));
		
		$("#brushColor").val(session.get("BRUSH_COLOR"));
		$("#brushColor").spectrum({
			color: this.selectedShape ? this.selectedShape.getBorderColor() : session.get("BRUSH_COLOR"),
			showInput: true,
			showPalette: true,
			palette: [
				['black', 'white', 'blanchedalmond'],
				['rgb(255, 128, 0);', 'hsv 100 70 50', 'lightyellow']
			],
			change: (function(color) {
				color = color.toString();
				$("#brushColor").val(color);
				if (this.selectedShape) {
					this.selectedShape.setBorderColor(color);
					this.canvas.getEngine().renderAll();
				} else {
					session.set("BRUSH_COLOR", color);
				}
				this.notifyObserver("On_brush_changed", {sender:this});
			}).bind(this)
		});
		
		$("#brushWidth").slider({
		  range: "min",
		  value: this.selectedShape ? this.selectedShape.getBorderWidth() : session.get("BRUSH_WIDTH"),
		  min: 1,
		  max: 10,
		  slide: (function(event, ui) {
			var val = ui.value;
			if (this.selectedShape) {
				this.selectedShape.setBorderWidth(val);
				this.canvas.getEngine().renderAll();
			} else {
				session.set("BRUSH_WIDTH", val);
			}
			this.notifyObserver("On_brush_changed", {sender:this});
		  }).bind(this)
		});
		
		if (this.selectedShape) {
			$('#btnBack').on('mousedown', (function(){
				this.selectedShape.sendBackwards()
			}).bind(this));
			$('#btnFront').on('mousedown', (function(){
				this.selectedShape.bringForward();
			}).bind(this));
			
			$('#shapeId').on('change', (
				function() {
					this.updateShapeId($('#shapeId').val());
				}
			).bind(this));
		}
	},
	
	/**
	 * Generate the controls and bind events for text
	 **/
	_initializeTextControls: function(){
		$("#textColorButton").val(this.selectedShape ? this.selectedShape.getBorderColor() : session.get("TEXT_COLOR"));
		$("#textColorButton").spectrum({
			color: this.selectedShape ? this.selectedShape.getBorderColor() : session.get("TEXT_COLOR"),
			showInput: true,
			showPalette: true,
			palette: [
				['black', 'white', 'blanchedalmond'],
				['rgb(255, 128, 0);', 'hsv 100 70 50', 'lightyellow']
			],
			change: (function(color) {
				color = color.toString();
				$("#textColorButton").val(color);
				if (this.selectedShape) {
					this.selectedShape.setBorderColor(color);
					this.selectedShape.setFillColor(color);
					this.canvas.getEngine().renderAll();
				} else {
					session.set("TEXT_COLOR", color);
				}
			}).bind(this)
		});
		
		$("#shapeOpacity").slider({
		  range: "min",
		  value: this.selectedShape ? (Number(this.selectedShape.getOpacity()) * 10) :(session.get("SHAPE_OPACITY") * 10),
		  min: 1,
		  max: 10,
		  slide: (function(event, ui) {
			if (this.selectedShape) {
				this.selectedShape.setOpacity(Number(ui.value)/10);
				this.canvas.getEngine().renderAll();
			} else {
				session.set("SHAPE_OPACITY", Number(ui.value)/10);
			}
		  }).bind(this)
		});
		
		if (this.selectedShape) {
			$('#btnBack').on('mousedown', (function(){
				this.selectedShape.sendBackwards();
			}).bind(this));
			
			$('#btnFront').on('mousedown', (function(){
				this.selectedShape.bringForward();
			}).bind(this));
			$('#shapeId').on('change', (
				function() {
					this.updateShapeId($('#shapeId').val());
				}
			).bind(this));
		}
	},
	
	/**
	 * Update selected shape id
	 * @param new id of shape
	 **/
	updateShapeId: function(id) {
		var message = "";
		if (!this.selectedShape) {
			message = "No shape is selected!";
		}
		if (id == "") {
			message = "The id of shape object can not be empty!";
		}
		if (this.canvas.getEngine().getShapeById(id) != null) {
			message = "There is already a shape object uses id '" + id + "'! Please change to use another one.";
		}
		
		if (message != "") {
			new Salomay.Application.Dialog().renderTo({
				title: "Error",
				container : $('#alertMessage'),
				innerHTML : "<span>" + message + "</span>",
				modal: true,
				buttons: {
					Ok: function() {
						$(this).dialog("close");
						$(this).html("");
					}
				}
			});
			$('#shapeId').val(this.selectedShape.getId());
			return;
		}
		
		// Update bind shapes
		var attachedById = this.selectedShape.getProperty("attachedBy");
		var attachToId = this.selectedShape.getProperty("attachTo");
		if (attachedById) {
			var bindShape = this.canvas.getEngine().getShapeById(attachedById);
			if (bindShape) {
				bindShape.setProperty("attachTo", $('#shapeId').val());
			}
		} else if (attachToId){
			var bindShape = this.canvas.getEngine().getShapeById(attachToId);
			if (bindShape) {
				bindShape.setProperty("attachedBy", $('#shapeId').val());
			}
		}
		// Update id
		this.selectedShape.setId($('#shapeId').val());
		this.canvas.refresh();
	}
},Salomay.Application.Widget.prototype);

/**
 * List panel for layers 
 * @author Cheng Li, Sep 09, 2013
 **/
Salomay.Application.LayersPanel = Class.create();
Salomay.Application.LayersPanel.prototype = Class.extend({
	/**
	 * Constructor
	 **/
	_initialize: function(options){
		 
	},
	
	/**
	 * Build toolbox layout html and render it to application, then build default events
	 **/
	_renderTo:function(options){
		var container = options.container;
		var shapes = options.shapes;
		var html = "<table id='layersContainer' width='100%' border='0' cellspacing='0' cellpadding='0'><tbody>";
		var rowHTML = "";
		for (var i=0; i < shapes.length; i++) {
			var className = "";
			if (i%2 == 0) {
				className = "app-grid-row";
			}else {
				className = "app-grid-alt-row";
			}
		    rowHTML += "<tr id='layer_tr_" + i +"' class='" + className + "' style='white-space:nowrap;' onmouseover='this.className=\"app-grid-select-row\"' onmouseout='this.className=\"" + className +"\"'><td style='border-bottom: 1px dotted #727272;'>";
			var shapeName = "";
			if (shapes[i].getProperty("isConnectionLine")) continue;
			switch (shapes[i].getShapeType()) {
				case Salomay.ShapeFactory.SHAPE_RECT:
					rowHTML += "<img id='layer_img_" + i +"' src='GraphicEngine/images/shape.jpg' width='18px' height='18px'/></td>";
					shapeName = "Rectangle";
				break;
				case Salomay.ShapeFactory.SHAPE_CIRCLE:
					rowHTML += "<img id='layer_img_" + i +"' src='GraphicEngine/images/shape.jpg'  width='18px' height='18px'/></td>";
					shapeName = "Circle";
				break;
				case Salomay.ShapeFactory.SHAPE_TRIANGLE:
					rowHTML += "<img id='layer_img_" + i +"' src='GraphicEngine/images/shape.jpg'  width='18px' height='18px'/></td>";
					shapeName = "Triangle";
				break;
				case Salomay.ShapeFactory.SHAPE_PATH:
					rowHTML += "<img id='layer_img_" + i +"' src='GraphicEngine/images/shape.jpg'  width='18px' height='18px'/></td>";
					shapeName = "Brush";
				break;
				case Salomay.ShapeFactory.SHAPE_TEXT:
					rowHTML += "<img id='layer_img_" + i +"' src='GraphicEngine/images/shape.jpg'  width='18px' height='18px'/></td>";
					shapeName = "Text";
				break;
				case Salomay.ShapeFactory.SHAPE_LINE:
					rowHTML += "<img id='layer_img_" + i +"' src='GraphicEngine/images/shape.jpg'  width='18px' height='18px'/></td>";
					shapeName = "Text";
				break;
				default:
					rowHTML += "<img src=''/></td>";
				break;
			}
			rowHTML += "<td id='layer_td_" + i +"' style='border-bottom: 1px dotted #727272;'><input id='layer_hidden_" + i +"' type='hidden' value='" + shapes[i].getProperty("id") + "'/><span id='layer_label_" + i +"' style='font-weight:bold;'>" + shapeName + " :</span> " + (shapes[i].getProperty("id") ? shapes[i].getProperty("id"):"") + "</td></tr>";
		}

		html = html + rowHTML + "</tbody></table>";
		container.html(html);
	},
	
	/**
	 * Reset toolbar status
	 **/
	_reset: function(){
	
	},
	
	/**
	 * Build events
	 **/
	_bindEvents: function(){
		var table = document.getElementById("layersContainer");
		var rows = table.getElementsByTagName("tr");
		for (var i=0; i < rows.length; i++) {
			rows[i].onmousedown = (function(evt) {
				var event = evt || window.event;
				var element = event.target || event.srcElement;
				var arry = element.id.split("_");
				if (arry.length <= 0) return;
				var index = arry[arry.length - 1];
				var shapeId = $('#layer_hidden_' + index).val();
				this.notifyObserver("On_layer_selected", {sender:this, shapeId:shapeId, event: event});
			}).bind(this);
		}
	}
},Salomay.Application.Widget.prototype);

/**
 * Page cover to disable whole application exception the top dialog
 * @note only one cover can be appended to application
 * @author Cheng Li, Sep 11, 2013
 **/
Salomay.Application.PageCover = {
	/**
	 * Show cover div on application
	 **/
	show: function(zIndex){
		return;
		if (!Salomay.Application.PageCover.coverDiv) {
			var zindex = 50;
			if (zIndex) {
				zindex = zIndex;
			}
			// Cover generator
			Salomay.Application.PageCover.coverDiv = document.createElement("div");
			Salomay.Application.PageCover.coverDiv.id = "coverDiv";
			Salomay.Application.PageCover.coverDiv.style.width = Salomay.Application.getComponentSize().width + "px";
			Salomay.Application.PageCover.coverDiv.style.height = Salomay.Application.getComponentSize().height + "px";
			Salomay.Application.PageCover.coverDiv.style.position = "absolute";
			Salomay.Application.PageCover.coverDiv.style.top = "0px";
			Salomay.Application.PageCover.coverDiv.style.left = "0px";
			Salomay.Application.PageCover.coverDiv.style.zIndex = zindex;
			Salomay.Application.PageCover.coverDiv.className = "app-cover";
			document.body.appendChild(Salomay.Application.PageCover.coverDiv);
		}
	},
	
	/**
	 * Show cover div on application
	 **/
	hide: function(){
		if (Salomay.Application.PageCover.coverDiv) {
			document.body.removeChild(Salomay.Application.PageCover.coverDiv);
			Salomay.Application.PageCover.coverDiv = null;
		}
	}
}

/**
 *	Getting start page display when application launched or user click open button
 *  @author Cheng Li Sep 11, 2013
 **/
Salomay.Application.GettingStart = Class.create();
Salomay.Application.GettingStart.prototype = Class.extend({
	/**
	 * Constructor
	 **/
	_initialize: function(options){
		this.innerWidth = 716;
		this.innerHeight = 461;
		this.hasCancelButton = false;
		if (options) {
			this.initialHide = options.initalHide;
			this.hasCancelButton = options.hasCancelButton;
		}
		this.containerWidth = this.innerWidth  + 1;
		this.containerHeight =  this.innerHeight + 4;
	},
	
	_renderTo:function(options){   
		var container = options.container;
		var top = 150;//(($(window).height() - this.containerHeight)/2) - 80;   
        var left = 400;//(($(window).width() - this.containerWidth)/2) - 100;
		container.css({position:"absolute", top:top, left:left, height:this.containerHeight, width: this.containerWidth});
		container.attr("class", "app-canvas-shadow");
		var html = "<div id='openWizardContainer' class='app-open-wizard-background' style='position:relative;width:" + this.innerWidth + "px;height:" + this.innerHeight + "px;z-index:99;'>"
				+ "<img src='GraphicEngine/images/chart.jpg' style='position:relative;left:400px;top:170px;width:230px;height:230px;'/>"
				+ "<div style='position:relative;top:-100px;left:50px;'><table><tr><td style='font-size:22pt;font-weight:bold;'>Annotation Studio</td></tr><tr><td style='font-size:10pt;'>Add annotation to your picture from web page</td></tr>"
				+ "<tr style='height:30px;'><td></td></tr>"
				+ "<tr><td><button id='openFromLocal' style='width:180px;height:30px' class='btn btn-info'>Open From Local File</button></td></tr>"
				+ "<tr style='height:3px;'><td></td></tr>"
				+ "<tr><td><button id='openFromServer' style='width:180px;height:30px' class='btn btn-info'>Open From Repository</button></td></tr>"
				+ "</table></div>"
				+ "<div style='position:relative;left:630px;top:20px;'><span id='closeGettingStart' style='font-size:13px;cursor:pointer;text-decoration:underline;'>Close</span></div>"
				+ "</div>";
		this.container = container;
		container.html(html);
		Salomay.Application.PageCover.show();
	},
	
	_reset: function(){
	
	},
	
	_bindEvents: function(){
		$('#closeGettingStart').on("click", (function(evt){
			this.hide(true);
		}
		).bind(this));
		$('#openFromLocal').on("click", (function(evt){
			this.hide(false);
		}
		).bind(this));
		$('#openFromServer').on("click", (function(evt){
			this.notifyObserver("On_open_from_server", {sender:this});
			this.hide();
		}
		).bind(this));
	},
	
	show: function(effect){
		if (effect) {
			this.container.fadeIn();
		} else {
			this.container.show();
		}
		Salomay.Application.PageCover.show();
	},
	
	hide: function(effect){
		if (effect) {
			this.container.fadeOut();
		} else {
			this.container.hide();
		}
		Salomay.Application.PageCover.hide();
	}
},Salomay.Application.Widget.prototype);

Salomay.Application.ImageRepositoryViewer = Class.create();
Salomay.Application.ImageRepositoryViewer.prototype = Class.extend({
	_initialize: function(options) {
		if (options) {
			this.model = options.model;
		}
	},
	
	_renderTo: function(options) {
		this.model = options.model;
		this.selectedDataIndex = this.model.get(0,"id").replace("thumbs_", "");
		this.container = options.container;
		var html = "<table><tr><td>"
		+ '<div id="gallery" class="content" style="background: rgba(255,255,255, 1);border-radius: 20px; padding:20px;border: 2px solid #a7a7a7;">'
		+			'<div class="slideshow-container">'
		+				'<div id="loading" class="loader"></div>'
		+				'<div id="slideshow" class="slideshow"></div>'
		+			'</div>'
		+ 		'</div>'
		+  '<div id="thumbs" class="navigation">'
		+  '<ul class="thumbs noscript" style="width:260px;">';

		for (var i = 0; i < this.model.RecordNumber; i++) {
				html += '<li style="border-bottom: 1px dotted #727272;">'
				html +=  '<a id="thumbs_' + this.model.get(i,"id") + '" class="thumb" href="' + this.model.get(i, "src") + '" title="' + this.model.get(i, "title") + '">' + this.model.get(i, "title") + '</a>'
				html += '</li>'
		}
		
		html += '</ul>';
		html += '</div>';
		html += '</td></tr><tr align="right"><td><table><tr><td><button id="viewerOpen" style="width:100px;height:30px" class="btn btn-success">Open</button></td><td><button id="viewerCancel" style="width:100px;height:30px" class="btn btn-info">Cancel</button></td></tr></table></td></tr></table>'
		this.dialog = new Salomay.Application.Dialog();
		this.dialog.renderTo({
					title: 'Image Repository',
					width: 1000,
					height: 640,
					container:options.container, 
					innerHTML:html,
					modal: true
		});
		
		$('div.navigation').css({'width' : '300px', 'float' : 'left', 'text-align' : 'left'});
		$('div.content').css('display', 'block');
		
		$('#thumbs').galleriffic({
					imageContainerSel:      '#slideshow',
					controlsContainerSel:   '#controls'
		});
		
	},
	
	_bindEvents: function(){
		var as = $('.thumb');
		for (var i=0; i<as.length; i++) {
			if(as[i].id.indexOf("thumbs_") == -1) continue;
			as[i].onclick = (function(evt) {
				var event = evt || window.event;
				var element = event.target || event.srcElement;
				var id = element.id;
				if (id) {
						id = id.replace("thumbs_", "");
						this.selectedDataIndex = id;
				}
			}).bind(this);
		}
		
		$('#viewerOpen').on("click", (function(evt){
			this.notifyObserver("On_loading_image", {
				sender:this, 
				bkImg:this.model.getById(this.selectedDataIndex,"src"), 
				archiveData: this.model.getById(this.selectedDataIndex,"archiveData"),
				imageWidth: this.model.getById(this.selectedDataIndex,"width"),
				imageHeight: this.model.getById(this.selectedDataIndex,"height")
			});
			this.dialog.close();
		}
		).bind(this));

	}
},Salomay.Application.Widget.prototype);

/** 
 * Salomay context menu
 * @author Cheng Li, Sep 14, 2013
 **/
Salomay.Application.ContextMenu = Class.create();
Salomay.Application.ContextMenu.prototype = Class.extend({
	_initialize: function(options) {
		
	},
	
	_renderTo: function(options) {
		this.container = options.container;
		this.shape = options.shape;
		this.canvas = options.canvas;
		this.top = options.top;
		this.left = options.left;
		if (!this.canvas || !this.container) return;
		
		this.container.css({"position":"absolute", "top": this.top, "left": this.left, "z-index":"92"});
		var html = '<ul id="menu">';
		if (!this.shape) {
			if (this.canvas.hasShapeToPaste()) {
				html += '<li id="contextMenu_paste_shape"><a href="#">Paste</a></li>';
			} else {
				//html += '<li id="contextMenu_paste_shape" class="ui-state-disabled"><a href="#">Paste</a></li>';
			}
			html += '<li id="contextMenu_showall_comment"><a href="#">Show all annotation</a></li>';
			html += '<li id="contextMenu_hideall_comment"><a href="#">Hide all annotation</a></li>';
		} else {
			if (this.shape.getProperty("attachedBy")) {
				 //html += '<li class="ui-state-disabled"><a href="#">Add annotation</a></li>';
				 if (this.shape.getProperty("attachedBy") && this.canvas.getEngine().getShapeById(this.shape.getProperty("attachedBy")).isVisible()) {
					 html += '<li id="contextMenu_hide_comment"><a href="#">Hide annotation</a></li>';
				 } else {
					 html += '<li id="contextMenu_show_comment"><a href="#">Show annotation</a></li>';
				 }
			} else if (!this.shape.getProperty("attachTo")) {
				 html += '<li id="contextMenu_add_comment"><a href="#">Add annotation</a></li>';
			}
			
			if (!this.shape.getProperty("attachTo")) {
				html += '<li id="contextMenu_copy_shape"><a href="#">Copy</a></li>';
				html += '<li id="contextMenu_cut_shape"><a href="#">Cut</a></li>';
			} else {
				html += '<li id="contextMenu_change_comment"><a href="#">Change annotation</a></li>';
			}
			html += '<li id="contextMenu_delete_shape"><a href="#">Delete</a></li>';
		}
		html += '</ul>';
		this.container.html(html);
		$( "#menu" ).menu();
	},
	
	close: function(){
		if (this.container) {
			this.container.html("");
		}
	},
	
	_bindEvents: function(){
		$('#contextMenu_add_comment').on("click", (function(evt){
			Salomay.Application.annotationEditor.renderTo({container:$('#annotationDlg'), shape:this.shape, isAddNew: true});
			this.close();
		}).bind(this));	
		
		$('#contextMenu_change_comment').on("click", (function(evt){
			Salomay.Application.annotationEditor.renderTo({container:$('#annotationDlg'), shape:this.shape, isAddNew: false});
			this.close();
		}).bind(this));
		
		$('#contextMenu_hide_comment').on("click", (function(evt){
			var anotherShape = this.canvas.getEngine().getShapeById(this.shape.getProperty("attachedBy"));
			if (anotherShape) {
				var line = this.shape.getProperty("connectionLine");
				line.hide();
				anotherShape.hide();
			}
			this.canvas.refresh();
			this.close();
		}).bind(this));
		
		$('#contextMenu_show_comment').on("click", (function(evt){
			var anotherShape = this.canvas.getEngine().getShapeById(this.shape.getProperty("attachedBy"));
			if (anotherShape) {
				var line = this.shape.getProperty("connectionLine");
				line.show();
				anotherShape.show();
			}
			this.canvas.refresh();
			this.close();
		}).bind(this));
		
		$('#contextMenu_cut_shape').on("click", (function(evt){
			this.canvas.cutShape(this.shape);
			this.canvas.refresh();
			this.close();
		}).bind(this));
		
		$('#contextMenu_copy_shape').on("click", (function(evt){
			this.canvas.copyShape(this.shape);
			this.close();
		}).bind(this));
		
		$('#contextMenu_paste_shape').on("click", (function(evt){
			this.canvas.pasteShape(evt);
			this.canvas.refresh();
			this.close();
		}).bind(this));
		
		$('#contextMenu_delete_shape').on("click", (function(evt){
			this.canvas.getEngine().removeShape({shape: this.shape});
			this.canvas.refresh();
			this.close();
		}).bind(this));
		
		$('#contextMenu_showall_comment').on("click", (function(evt){
			this.canvas.showAllAnnotation();
			this.canvas.refresh();
			this.close();
		}).bind(this));
		
		$('#contextMenu_hideall_comment').on("click", (function(evt){
			this.canvas.hideAllAnnotation();
			this.canvas.refresh();
			this.close();
		}).bind(this));
	}
},Salomay.Application.Widget.prototype);


/** 
 * Salomay Text Editor
 * @author Cheng Li, Sep 24, 2013
 **/
Salomay.Application.TextEditor = Class.create();
Salomay.Application.TextEditor.prototype = Class.extend({
	_initialize: function(options) {
		
	},
	
	_renderTo: function(options) {
		this.container = options.container;
		this.container.css({position:"absolute", top:options.top, left:options.left, height:options.height, width: options.width});
		var html = "<textarea id='textEditor' type='text' style='width:100%;height:100%;'/>";
		this.container.html(html);
		$('#textEditor').click();
	},
	
	_bindEvents: function(){
		
	}
},Salomay.Application.Widget.prototype);

/**
 * Widget of annotation editor dialog
 * @author Cheng Li Sep 14, 2013
 */
Salomay.Application.AnnotationEditor = Class.create();
Salomay.Application.AnnotationEditor.prototype = Class.extend({
	_initialize: function(options) {
		
	},
	
	_renderTo: function(options) {
		this.container = options.container;
		this.shape = options.shape;
		this.isAddNew = options.isAddNew;
		var txtValue = '';
		var html = "<table><tr><td colspan='2'><textarea id='annotationEditor' style='height:200px;width:300px;'/></td></tr>";
			html += "<tr><td align='right'><button id='okAnnotation' style='width:70px;height:30px' class='btn btn-success'>Submit</button></td><td><button id='cancelAnnotation' style='width:70px;height:30px' class='btn btn-success'>Cancel</button></td></tr></table>";
		this.dialog = new Salomay.Application.Dialog();
		this.dialog.renderTo({
					title: 'Annotation Editor',
					width: 350,
					height: 330,
					container:options.container, 
					innerHTML:html,
					modal: true
		});
		if (!this.isAddNew && this.shape.getShapeType() == Salomay.ShapeFactory.SHAPE_TEXT) {
			$('#annotationEditor').val(this.shape.getText());
		}
	},
	
	_bindEvents: function(){
		$('#okAnnotation').on("click", (function(evt){
			if (this.isAddNew) {
				this.notifyObserver("On_add_annotation", {sender:this, shape:this.shape, text:$('#annotationEditor').val()});
			} else {
				this.notifyObserver("On_change_annotation", {sender:this, shape:this.shape, text:$('#annotationEditor').val()});
			}
			this.dialog.close();
		}).bind(this));	
		
		$('#cancelAnnotation').on("click", (function(evt){
			this.dialog.close();
		}).bind(this));	
	}
},Salomay.Application.Widget.prototype);



/**
 ** Abstract super class for model entity class
 ** This class supplies basic data manipulation like sort by, search
 ** You can extend this class by inherit it and add your
 ** Database access logic by overide the Load() method.
 ** You can also attach controller or view object to the model object
 ** so that them can observe the change of model object and react
 ** @author Cheng Li, Sep 12, 2013
 **/
Salomay.Application.Model = Class.create();
Salomay.Application.Model.prototype = {
  /**
   ** Constructor
   **/
  initialize: function(options){
    this.observerManager = new ObserverManager();
    this.Columns = [];
    this.RecordNumber = 0;
    if(options.columns){
        this.setColumns(options.columns)
    }
    this.JSONStore = new Array();
    this.data = new Array();
  },
  
  /**
   ** Attach a view or controller to this model object and make them observe model refresh event
   **/
  attach:function(listener){
    this.addObserver("refresh",listener,listener.On_model_refresh);
  },
  
  /**
   ** Detach view or controller from model
   **/
  detach:function(listener) {
	// Implementation
	this.stopObserving("refresh", listener, listener.On_model_refresh);
  },
  
  /**
   ** Fire refresh event and notify all observers the model has been changed
   **/
  refresh:function(){
	this.OnRefresh();
  },
  
  /**
   ** Fire refresh event and notify all observers the model has been changed
   **/
  OnRefresh:function(){
     var context = new Object();
     context.sender = this;
     context.requestAction = "model_refresh";
     this.notifyObserver("refresh",context);
  },
  
  /**
   ** Initialize the model data structure
   **/
  setColumns:function(columns){
        for(i=0;i<columns.length;i++){
            this.Columns.push(columns[i].name);
            this.Columns[columns[i].name]={
                index:i,
                name:columns[i].name,
                type:columns[i].type,
                length:columns[i].length
            };
        }
  },
  
  /**
   ** Load data and fire pre-load data event
   **/
  loadData:function(request){
    this.notifyObserver("pre_load_data",request);
    this.loadData(request);
  },
  
  /**
   ** Load data without firing pre-load data event
   **/
  load:function(request){
    this.data = request.data;
    this.RecordNumber = this.data.length;
    this.notifyObserver("post_load_data",request);
  },
  
  /**
   ** Parse JSON data and fill into inner data array
   **/
  fillStore:function(originalResponse){
    try{
        var response = eval('('+originalResponse.responseText+')');
        this.JSONStore = response;
        this.data = this.JSONStore.data;
        this.RecordNumber = this.JSONStore.data.length;
    }catch(e){ }
    this.notifyObserver("post_load_data",this);
    this.OnRefresh();
  },
  
  /**
   ** Entry function for sorting model data
   **/
  sortBy:function(column_name,direction_code){
    this.notifyObserver("pre_sort_data",this);
    setTimeout(this.doSort.bind(this,column_name,direction_code),0);
  },
  
  /**
   ** Sortor method
   **/
  doSort:function(column_name,direction_code){
    var direction_code = (direction_code)?direction_code:"ASC";
    var column_index = this.Columns[column_name].index;
    var column_type = this.Columns[column_name].type;
    var data = this.data;
    var sort_fn = function(left,right){
       var result = 0;
       var left_val = left[column_index]
       var right_val = right[column_index]
        if (column_type=="N"){
          left_val = parseFloat(left_val);
          right_val = parseFloat(right_val);  
        }
        if(left_val>right_val){
            result = (direction_code=="ASC")?1:-1;
        }
        if(left_val<right_val){
            result = (direction_code=="ASC")?-1:1;
        }
        return result;
    }
    this.data.sort(sort_fn);
    sort_fn = null;
    this.notifyObserver("post_sort_data",this);
    this.OnRefresh();
  },
  
  /**
   ** Get a data in specific row and colum
   **/
  get:function(row_number,column_name){
    var result = "";
    try{
       var column_index = this.Columns[column_name].index;
       result = this.data[row_number][column_index];
    }catch(e){
        
    }
    return result;
  },
  
  /**
   * Get specific column by id
   * @param id - Id of specific row
   * @param column_name which column you want to return
   * @return return value of specific column
   **/
  getById:function(id, column_name) {
	var idIdx = this.Columns["id"].index;
	var result = null;
	for (var i=0; i<this.data.length;i++) {
		if(this.get(i, "id") == id) {
			result = this.get(i,column_name);
			break;
		}
	}
	return result;
  },
  
  /**
   ** Get a specific row
   **/
  getRow:function(row_number){
    return this.data[row_number];
  },
  
  /**
   ** Set data into model
   **/
  set:function(row_number,column_name,new_value) {
	try{
       var column_index = this.Columns[column_name].index;
       this.data[row_number][column_index] = new_value;
    }catch(e){
        
    }
  },
  
 	/**
     * Add event lisenter for the instance
	 * @param event_name the name of observing event
	 * @param evecuter the this pointer for who call the method
	 * @param method event handler
     **/
    addObserver:function(event_name,executer,method){
	    var observer = new Observer(executer,method);
	    this.observerManager.AddObserver(event_name,observer);
    },
	  
    /**
     * Stop observe event
	 * @param event_name the name of observing event
	 * @param evecuter the this pointer for who call the method
	 * @param method event handler
     **/
    stopObserving:function(event_name,executer,method){
        var observer = new Observer(executer,method);
        this.observerManager.StopObserving(event_name,observer);
    },
  
    /**
     * Trigger event by giving event name
	 * @param event_name the name of observing event
	 * @param context context obect contains runtime variables
     **/
    notifyObserver:function(event_name,context){
        this.observerManager.NotifyAllObservers(event_name,context);
    }
}


Salomay.Application.OpearationCache = Class.create();
Salomay.Application.OpearationCache.prototype = {
	initialize: function(options) {
		this.cacheSize = 5;
		this.currentCacheIndex = 0;
		this.cache = new Array();
	},
	
	getCache: function(index) {
		if (index >= 0 && index < this.cacheSize) {
			this.currentCacheIndex = index;
			return this.cache[index];
		}
		return null
	},
	
	putCache: function(data) {
		if (this.cache.length > 0 && data == this.cache[this.length-1]) return;
		if (this.cache.length < this.cacheSize) {
			this.cache[this.length] = data;
		} else {
			for (var i=0; i < this.cacheSize; i++) {
				if (i == (this.cacheSize -1)) {
					this.cache[i] = data;
				} else {
					this.cache[i] = this.cache[i+1];
				}
			}
		}
		this.currentCacheIndex = this.cache.length;
	}
}

